﻿using Forum.WebAPI.Attributes;
using OnlineTestsDataLayer;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Cors;
using System.Web.Http.ValueProviders;
using OnlineTestsModels;
using OnlineTestsRest.Models.TestModels;
using OnlineTestsRest.Models.QuestionModels;
using OnlineTestsRest.Models.AnswerModels;

namespace OnlineTestsRest.Controllers
{
    [EnableCors(origins: "*", headers: "*", methods: "*")]
    public class TestsController : ApiController
    {
        private const int SessionKeyLength = 50;
        private const int QuestionsPerTest = 20;
        private const int AnswersPerQuestion = 4;
        private const int OffsetMinutes = 2;

        [HttpGet]
        [Route("api/tests/all/")]
        public HttpResponseMessage GetAllAvailableTests(
              [ValueProvider(typeof(HeaderValueProviderFactory<string>))] string SessionKey)
        {
            if (!this.ValidateSessionKey(SessionKey))
            {
                return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Missing or invalid session key length");
            }

            TestsContext db = new TestsContext();

            User user = db.Users.FirstOrDefault(u => u.SessionKey == SessionKey);

            if (user == null)
            {
                return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid session key");
            }


            var tests = db.Tests.Include("UsersToTest")
                .Where(t => t.StartDate < DateTime.Now && t.EndDate > DateTime.Now
                    && !t.UsersToTest.Any(ut => ut.UserId == user.Id))
                .OrderByDescending(t => t.StartDate)
                .Select(TestShortModel.FromTest)
                .ToList();

            return this.Request.CreateResponse(HttpStatusCode.OK, new TestCollectionWrapper(tests));
        }

        [HttpPost]
        [Route("api/tests/start/")]
        public HttpResponseMessage PostStart([FromBody] StartTestModel startTest,
             [ValueProvider(typeof(HeaderValueProviderFactory<string>))] string SessionKey)
        {
            int id = startTest.Id;
            string password = startTest.Password;

            if (!this.ValidateSessionKey(SessionKey))
            {
                return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Missing or invalid session key length");
            }

            TestsContext db = new TestsContext();

            User user = db.Users.FirstOrDefault(u => u.SessionKey == SessionKey);

            if (user == null)
            {
                return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid session key");
            }

            Test testEntity = db.Tests.FirstOrDefault(t => t.Id == id);

            if (testEntity == null)
            {
                return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Test not found");
            }

            if (testEntity.Password != password)
            {
                return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Wrong test password");
            }

            if (testEntity.UsersToTest.Any(ut => ut.UserId == user.Id))
            {
                return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "User has already started this test");
            }

            if (testEntity.StartDate > DateTime.Now)
            {
                return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest,
                    string.Format("Test will be active from {0:dd/MM/yyyy hh:mm:ss} to {1:dd/MM/yyyy hh:mm:ss}",
                        testEntity.StartDate, testEntity.EndDate));
            }

            if (testEntity.EndDate < DateTime.Now)
            {
                return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Test is not active anymore");
            }

            db.UsersToTests.Add(new UserToTest()
            {
                User = user,
                Test = testEntity,
                StartDate = DateTime.Now,
            });

            db.SaveChanges();

            TestDetailModel model = TestDetailModelFromEntity(testEntity);

            return this.Request.CreateResponse(HttpStatusCode.OK,
                (new TestCollectionWrapper(new List<TestDetailModel>() { model })));
        }

        [HttpGet]
        [Route("api/tests/started")]
        public HttpResponseMessage GetStartedTests(
            [ValueProvider(typeof(HeaderValueProviderFactory<string>))] string SessionKey)
        {
            if (!this.ValidateSessionKey(SessionKey))
            {
                return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Missing or invalid session key length");
            }

            TestsContext db = new TestsContext();

            User user = db.Users.FirstOrDefault(u => u.SessionKey == SessionKey);

            if (user == null)
            {
                return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid session key");
            }

            var tests = db.Tests.Include("UsersToTest")
                .Where(t => t.StartDate < DateTime.Now && t.EndDate > DateTime.Now
                    && t.UsersToTest.Any(ut => ut.UserId == user.Id))
                .OrderByDescending(t => t.StartDate)
                .Select(TestShortModel.FromTest)
                .ToList();

            return this.Request.CreateResponse(HttpStatusCode.OK, new TestCollectionWrapper(tests));
        }

        [HttpGet]
        [Route("api/tests/started/{id}")]
        public HttpResponseMessage GetStartedTest(int id,
            [ValueProvider(typeof(HeaderValueProviderFactory<string>))] string SessionKey)
        {
            if (!this.ValidateSessionKey(SessionKey))
            {
                return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Missing or invalid session key length");
            }

            TestsContext db = new TestsContext();

            User user = db.Users.FirstOrDefault(u => u.SessionKey == SessionKey);

            if (user == null)
            {
                return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid session key");
            }

            Test testEntity = db.Tests.FirstOrDefault(t => t.Id == id);

            if (testEntity == null)
            {
                return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Test not found");
            }

            if (!testEntity.UsersToTest.Any(ut => ut.UserId == user.Id))
            {
                return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "User hasn't started this test");
            }

            if (testEntity.EndDate < DateTime.Now)
            {
                return this.Request.CreateResponse(HttpStatusCode.BadRequest, "Test isn't active anymore");
            }

            TestDetailModel model = TestDetailModelFromEntity(testEntity);

            return this.Request.CreateResponse(HttpStatusCode.OK,
                (new TestCollectionWrapper(new List<TestDetailModel>() { model })));
        }

        [HttpPost]
        [Route("api/tests/finish")]
        public HttpResponseMessage FinishTest([FromBody] FinishTestModel finishTestModel,
            [ValueProvider(typeof(HeaderValueProviderFactory<string>))] string SessionKey)
        {
            var endDate = DateTime.Now;

            if (!this.ValidateSessionKey(SessionKey))
            {
                return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Missing or invalid session key length");
            }

            TestsContext db = new TestsContext();

            User user = db.Users.FirstOrDefault(u => u.SessionKey == SessionKey);

            if (user == null)
            {
                return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid session key");
            }

            Test testEntity = db.Tests.Include("Questions")
                .Include("Questions.Answers")
                .FirstOrDefault(t => t.Id == finishTestModel.Id);

            if (testEntity == null)
            {
                return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Test not found");
            }

            var userToTest = testEntity.UsersToTest.FirstOrDefault(ut => ut.UserId == user.Id);

            if (userToTest == null)
            {
                return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "User hasn't started this test");
            }

            if (testEntity.EndDate.AddMinutes(OffsetMinutes) < DateTime.Now)
            {
                return this.Request.CreateResponse(HttpStatusCode.BadRequest, "Test isn't active anymore");
            }

            if (userToTest.EndDate != null)
            {
                return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "User already finished this test");
            }

            if ((endDate - userToTest.StartDate.Value).TotalMinutes >
                testEntity.DurationMinutes + OffsetMinutes)
            {
                return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "You exceeded max allowed test duration");
            }

            userToTest.EndDate = endDate;

            int questionsCount = 0;
            int resultForTest = 0;
            int maxQuestions = Math.Min(QuestionsPerTest, testEntity.Questions.Count);

            // Stupid result count ... 
            // User can hack it if send some fake finish questions collection
            // w/e enough for demo

            foreach (var qFinish in finishTestModel.Questions)
            {
                if (++questionsCount > maxQuestions) break;

                var qEntity = testEntity.Questions.FirstOrDefault(q => q.Id == qFinish.QuestionId);

                if (qEntity != null)
                {
                    if (qEntity.CorrectAnswerId == qFinish.AnswerId)
                    {
                        resultForTest++;
                    }
                }
            }

            userToTest.Result = resultForTest;
            db.SaveChanges();

            string responseResult = resultForTest + "/" + maxQuestions;

            return this.Request.CreateResponse(HttpStatusCode.OK, new { Result = responseResult });
        }

        [HttpGet]
        [Route("api/tests/statistics")]
        public HttpResponseMessage Statistics(
            [ValueProvider(typeof(HeaderValueProviderFactory<string>))] string SessionKey)
        {
            if (!this.ValidateSessionKey(SessionKey))
            {
                return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Missing or invalid session key length");
            }

            TestsContext db = new TestsContext();

            User user = db.Users.FirstOrDefault(u => u.SessionKey == SessionKey);

            if (user == null)
            {
                return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid session key");
            }

            var tests = db.Tests.Where(t => t.UsersToTest
                    .Any(ut => ut.UserId == user.Id && ut.EndDate != null && ut.Result != null))
                .Select(t => new TestResultModel
                {
                    Name = t.Name,
                    Result = t.UsersToTest.FirstOrDefault(ut => ut.UserId == user.Id).Result,
                    Questions = t.Questions.Count
                }).ToList();

            for (int i = 0; i < tests.Count; i++)
            {
                tests[i].Questions = Math.Min(tests[i].Questions, QuestionsPerTest);
            }

            return this.Request.CreateResponse(HttpStatusCode.OK, new { Tests = tests });
        }

        private TestDetailModel TestDetailModelFromEntity(Test testEntity)
        {
            TestDetailModel model = new TestDetailModel
            {
                Id = testEntity.Id,
                Name = testEntity.Name,
                StartDate = testEntity.StartDate,
                EndDate = testEntity.EndDate,
                Duration = testEntity.DurationMinutes,
                Questions = testEntity.Questions
                    .OrderBy(q => Guid.NewGuid())
                    .Take(QuestionsPerTest)
                    .Select(q => new QuestionModel()
                    {
                        Id = q.Id,
                        Title = q.Title,
                        Answers = q.Answers
                            //.Where(a => a.Id != q.CorrectAnswerId)
                            .OrderBy(a => Guid.NewGuid())
                            //.Take(AnswersPerQuestion - 1)
                            .Select(a => new AnswerModel()
                            {
                                Id = a.Id,
                                Content = a.Content
                            }),
                    })
            };

            // TODO: only 4 answers per questions (one of them correct)

            //TestsContext db = new TestsContext();

            //for (int i = 0; i < model.Questions.Count(); i++)
            //{
            //    var q = model.Questions.ElementAt(i);
            //    var theAnswId = db.Questions.Find(q.Id).CorrectAnswerId;
            //    var theAnsw = db.Answers.Find(theAnswId);
            //    q.Answers.Add(new AnswerModel() { Id = theAnsw.Id, Content = theAnsw.Content }); // IEnumerable -> ICollection or better logic
            //}

            return model;
        }

        private bool ValidateSessionKey(string sessionKey)
        {
            if (string.IsNullOrWhiteSpace(sessionKey) ||
                sessionKey.Length != SessionKeyLength)
            {
                return false;
            }
            else
            {
                return true;
            }
        }
    }
}
